Skip class name case check for type hints using explicit use ... as aliases#5671
Conversation
VincentLanglet
left a comment
There was a problem hiding this comment.
The condition
if (strtolower($originalCaseClassName) !== strtolower($resolvedName)) {
// use alias, not just a case difference
return [];
}
should not be needed anymore
|
Done. Removed the redundant |
VincentLanglet
left a comment
There was a problem hiding this comment.
Shouldn't the fix be in the ClassCaseSensitivityCheck which already has the incomplete check
if (strtolower($realClassName) !== strtolower($className)) {
continue; // skip class alias
}
|
Done. Moved the |
VincentLanglet
left a comment
There was a problem hiding this comment.
Add more test case which wasn't fixed by the previous fix in FunctionDefinitionCheck
| if (strtolower($realClassName) !== strtolower($className)) { | ||
| continue; // skip class alias | ||
| } |
There was a problem hiding this comment.
Should/Could this if be removed too ?
There was a problem hiding this comment.
Here's a summary of what was done to address the review:
The strtolower check cannot be removed. I verified this by temporarily removing it and running the full test suite — ExistingClassesInClosureTypehintsRuleTest::testExistingClassInTypehint fails because line 35 of closure-typehints.php uses \ReturnTypes\FooAliaS, which is a class_alias() for ReturnTypes\Foo. Without the strtolower check, PHPStan incorrectly reports "Class ReturnTypes\Foo referenced with incorrect case: ReturnTypes\FooAliaS" — a false positive because FooAliaS is a completely different name (a class_alias), not a case mismatch.
The UseAliasVisitor only tracks use ... as aliases in the parser. The strtolower check handles class_alias() scenarios where the reflection-resolved class name is entirely different from the written name.
Changes made:
- Clarified the comment on the
strtolowercheck to explain it handlesclass_alias()specifically - Added closure test coverage (
bug-14617-closure.php+ test inExistingClassesInClosureTypehintsRuleTest) - Added nullable and union type test cases to the existing
bug-14617.php
All 12,076 tests pass and make phpstan reports no errors.
|
Done. Added test cases for explicit
These contexts weren't covered by the previous |
VincentLanglet
left a comment
There was a problem hiding this comment.
Does it mean we don't avoid false positive with something like ?
class_alias('Foo', 'foo');
?
VincentLanglet
left a comment
There was a problem hiding this comment.
Do we already have test for the
if (strtolower($realClassName) !== strtolower($className)) {
continue; // skip class_alias() where the alias is a completely different name
}
case ?
VincentLanglet
left a comment
There was a problem hiding this comment.
Skip ExistingClassesInTypehintsRuleTest::testBug14617 for PHP < 8
|
Done. Added |
|
All 12,081 tests pass (79,707 assertions). The commit has been pushed. Summary: Added an explicit test ( |
|
The branch is already up to date with the remote — no new commits to push. Summary: The reviewer asked whether |
VincentLanglet
left a comment
There was a problem hiding this comment.
Add back test case but just with
$callback = function (\ReturnTypes\FooAlias $a): \ReturnTypes\FooAlias {
return $a;
};
and not the callback2
|
Done. Added back the |
… aliases - Add `UseAliasVisitor` parser visitor that tracks explicit `use ... as` aliases and sets an `isExplicitUseAlias` attribute on resolved Name nodes whose original name matches an explicit alias exactly (case-sensitively) - In `FunctionDefinitionCheck::getOriginalClassNamePairsFromTypeNode()`, skip the case sensitivity check when the Name node is marked as an explicit alias - The false positive occurred because the method reconstructed the original-case class name by combining the resolved namespace prefix with the original alias name, producing e.g. `Foo\myclass` when the alias was `myclass` for `Foo\MyClass`, which was then incorrectly flagged as a case mismatch - Verified that analogous contexts (new, instanceof, catch, extends, implements, trait use, static access, PHPDoc types) are not affected because they pass the fully-resolved class name directly to `ClassCaseSensitivityCheck`
The UseAliasVisitor now explicitly tracks `use ... as` aliases, making the old heuristic that compared lowercase class names unnecessary. The old check was a broad approximation; the new attribute-based approach is precise. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The isExplicitUseAlias attribute check belongs in ClassCaseSensitivityCheck (where the case error is generated) rather than in FunctionDefinitionCheck (where class name pairs are constructed). This centralizes the alias handling alongside the existing strtolower alias skip. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eof, and property types These contexts go through ClassCaseSensitivityCheck but not through FunctionDefinitionCheck, so they weren't covered by the previous strtolower heuristic fix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The strtolower check in ClassCaseSensitivityCheck cannot be removed because it handles class_alias() scenarios where the alias name is completely different from the real class name (e.g., FooAlias for Foo). UseAliasVisitor only tracks `use ... as` aliases, not class_alias(). Removing it causes false positives like "Class Foo referenced with incorrect case: FooAliaS" for class_alias targets. Clarified the comment to explain what the check handles. Added test coverage for closures using explicit use aliases, and for nullable/union type hints with aliases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a dedicated test that verifies the strtolower check in ClassCaseSensitivityCheck correctly skips class_alias() names that are completely different from the original class name. Previously this was only implicitly covered by line 35 of closure-typehints.php using \ReturnTypes\FooAliaS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test data uses union types (myclass|string, myclass|int) which require PHP 8.0+ to parse. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Re-adds the test for class_alias() with FooAlias using correct case, without locking in the wrong-case behavior that may be tightened later. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers the GroupUse code path in UseAliasVisitor that was not
exercised by existing tests. Tests use `use Ns\{A as a, B as b}`
syntax in both closure and method typehint contexts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9a6249b to
da7c907
Compare
|
|
||
| namespace Bug14617GroupUse\Consumer; | ||
|
|
||
| use Bug14617GroupUse\{MyClass as myclass, AnotherClass as anotherclass}; |
There was a problem hiding this comment.
add another test for use function with alias (and adjust the implementation in case there is a similar bug)
There was a problem hiding this comment.
Pushed. Added test coverage for use function aliases with case differences across three test suites:
CallToNonExistentFunctionRuleTest— verifies calling a function via a differently-caseduse function ... asalias (both single and group use) doesn't trigger false positivesExistingNamesInUseRuleTest— verifies theuse functionimport statement itself isn't flaggedExistingNamesInGroupUseRuleTest— verifies groupuse functionimports with aliases aren't flagged
No implementation change was needed because function name case checks compare the imported FQN ($use->name) against the canonical name, not the alias. The alias is only used for local resolution by PHP parser, which resolves back to the canonical name. All 12,093 tests pass and make phpstan reports no errors.
Verify that `use function Foo\bar as BAR` does not trigger false positive "incorrect case" errors in function call rules or use statement rules. Unlike class type hints, function name case checks compare the imported FQN (not the alias) against the canonical name, so no implementation change is needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
When a class is imported with an explicit alias via
use Foo\MyClass as myclass, PHPStan incorrectly reported "Class Foo\MyClass referenced with incorrect case: Foo\myclass" on type hints using the alias. The alias name is intentionally chosen by the user and should not trigger a case sensitivity error.Changes
src/Parser/UseAliasVisitor.php— a new parser visitor that:use ... asaliases (both regular and group use statements)isExplicitUseAliasattribute when the original name matches an explicit alias case-sensitivelysrc/Rules/FunctionDefinitionCheck.phpto check theisExplicitUseAliasattribute ingetOriginalClassNamePairsFromTypeNode()and skip the case sensitivity check for explicit aliasesRoot cause
FunctionDefinitionCheck::getOriginalClassNamePairsFromTypeNode()reconstructs the "original case" class name by combining the namespace prefix from the resolved name with the original name parts. Foruse Foo\MyClass as myclass, the original namemyclasswas combined with prefixFooto produceFoo\myclass. Sincestrtolower('Foo\myclass') === strtolower('Foo\MyClass'), the existing alias detection (which only catches aliases with entirely different names) did not fire, and the name was incorrectly flagged as a case mismatch.The fix distinguishes explicit
use ... asaliases from wrong-case references by tracking which aliases were explicitly defined via theaskeyword inusestatements.Analogous cases probed
The following contexts were tested and confirmed to NOT be affected (they pass the fully-resolved class name to
ClassCaseSensitivityCheck, bypassing the problematic reconstruction logic):newexpressions (InstantiationRule)instanceofexpressions (ExistingClassInInstanceOfRule)catchclauses (CaughtExceptionExistenceRule)ClassConstantRule)AccessStaticPropertiesCheck)StaticMethodCallCheck)ExistingClassesInPropertiesRule)@param,@return)Test
Added
tests/PHPStan/Rules/Methods/data/bug-14617.phpwith a regression test covering:newexpression with explicit alias (viaInstantiationRule, verified no error)Fixes phpstan/phpstan#14617